package otSpaces.server;

import java.io.Serializable;
import java.math.BigInteger;
import java.nio.ByteBuffer;

import otSpaces.server.util.ReferenceMapping;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.Channel;
import com.sun.sgs.app.ChannelListener;
import com.sun.sgs.app.ChannelManager;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.Delivery;

interface OtSpaceManager extends ChannelManager, Serializable {

	static class TSpaceManagerImpl implements Serializable, OtSpaceManager {

		private static final long serialVersionUID = -4084519999017207762L;

		private final ReferenceMapping.Ref<BigInteger, OtSpaceSubscriber> _clientMapRef;
		private final ReferenceMapping.Ref<String, OtSpaceTopic> _topicMapRef;

		public final String _toSpaceName;

		public TSpaceManagerImpl(String toSpaceName) {
			_toSpaceName = toSpaceName;

			_topicMapRef = new ReferenceMapping.Ref<String, OtSpaceTopic>(true);

			_clientMapRef = new ReferenceMapping.Ref<BigInteger, OtSpaceSubscriber>(
					true);

		}

		/**
		 * This will ONLY return a fully joined subscriber
		 * 
		 */
		OtSpaceSubscriber accessSubscriber(final BigInteger id) {

			// grab the map
			final ReferenceMapping<BigInteger, OtSpaceSubscriber> map = _clientMapRef
					.get();

			// if map !contains
			if (!map.containsKey(id)) {

				// return null
				return null;
			}

			// get from map
			final OtSpaceSubscriber subscriber = map.get(id);

			// if it fails the check
			if (subscriber == null || subscriber.getClientSession() == null) {

				// remove it
				map.put(id, null);
				map.remove(id);

				// return null
				return null;
			}

			// return what we got
			return subscriber;
		}

		@Override
		public BigInteger clientId(ClientSession clientSession) {

			// get the id
			final BigInteger clientId = AppContext.getDataManager()
					.getObjectId(clientSession);

			// get the subscriber (with our private)
			final OtSpaceSubscriber subscriber = accessSubscriber(clientId);

			// if null ...
			if (subscriber == null) {

				// ... then create a new one
				_clientMapRef.getForUpdate().put(clientId,
						new OtSpaceSubscriber(clientSession, getName()));

				assert accessSubscriber(clientId) != null;
				assert accessSubscriber(clientId).getClientSession() != null;
				assert accessSubscriber(clientId).getClientSession() == clientSession;
			}

			return clientId;
		}

		@Override
		public boolean clientJoin(BigInteger clientId, String topicName) {

			final OtSpaceSubscriber subscriber = accessSubscriber(clientId);

			if (subscriber != null) {

				subscriber.sendJoin(topicName);
				return true;
			} else {
				return false;
			}
		}

		@Override
		public boolean clientLeave(BigInteger clientId, String topicName) {
			final OtSpaceSubscriber subscriber = accessSubscriber(clientId);

			if (subscriber != null) {
				subscriber.sendLeave(topicName);
				return true;
			} else {
				return false;
			}
		}

		@Override
		public boolean clientSend(BigInteger clientId, String topicName,
				ByteBuffer message) {
			final OtSpaceSubscriber subscriber = accessSubscriber(clientId);

			if (subscriber != null) {
				subscriber.sendMessage(topicName, message);
				return true;
			} else {
				return false;
			}
		}

		@Override
		public ClientSession clientSession(BigInteger id) {

			final OtSpaceSubscriber subscriber = accessSubscriber(id);

			return subscriber == null ? null : subscriber.getClientSession();
		}

		@Override
		public Channel createChannel(String name, ChannelListener listener,
				Delivery delivery) {

			if (delivery != null && delivery != Delivery.RELIABLE) {
				throw new IllegalArgumentException();
			}
			if (_topicMapRef.get().containsKey(name)) {
				throw new IllegalArgumentException();
			}

			final OtSpaceTopic spaceTopic = new OtSpaceTopic(listener, this,
					name);

			_topicMapRef.getForUpdate().put(name, spaceTopic);

			return spaceTopic;
		}

		@Override
		public Channel getChannel(String name) {

			return _topicMapRef.get().get(name);
		}

		@Override
		public String getName() {
			return _toSpaceName;
		}
	}

	BigInteger clientId(final ClientSession clientSession);

	boolean clientJoin(final BigInteger clientId, final String topicName);

	boolean clientLeave(final BigInteger clientId, final String topicName);

	boolean clientSend(final BigInteger clientId, final String topicName,
			final ByteBuffer message);

	ClientSession clientSession(final BigInteger id);

	public String getName();
}
